#include "ffhttpd.h"

static char *g_ffhttpd_head1 =
	"HTTP/1.1 %s\r\n"
	"Server: ffhttpd/1.0.0\r\n"
	"Accept-Ranges: bytes\r\n"
	"Content-Type: %s\r\n"
	"Content-Length: %d\r\n"
	"Connection: close\r\n\r\n";

static char *g_ffhttpd_head2 =
	"HTTP/1.1 206 Partial Content\r\n"
	"Server: ffhttpd/1.0.0\r\n"
	"Content-Range: bytes %d-%d/%d\r\n"
	"Content-Type: %s\r\n"
	"Content-Length: %d\r\n"
	"Connection: close\r\n\r\n";

static char *g_404_page =
	"<html>\r\n"
	"<head><title>404 Not Found</title></head>\r\n"
	"<body>\r\n"
	"<center><h1>404 Not Found</h1></center>\r\n"
	"<hr><center>ffhttpd/1.0.0</center>\r\n"
	"</body>\r\n"
	"</html>\r\n";

static char *g_content_type_list[][2] = {
	{".asf", "video/x-ms-asf"},
	{".avi", "video/avi"},
	{".bmp", "application/x-bmp"},
	{".css", "text/css"},
	{".exe", "application/x-msdownload"},
	{".gif", "image/gif"},
	{".htm", "text/html"},
	{".html", "text/html"},
	{".ico", "image/x-icon"},
	{".jpeg", "image/jpeg"},
	{".jpg", "image/jpeg"},
	{".mp3", "audio/mp3"},
	{".mp4", "video/mp4"},
	{".pdf", "application/pdf"},
	{".png", "image/png"},
	{".ppt", "application/x-ppt"},
	{".swf", "application/x-shockwave-flash"},
	{".tif", "image/tiff"},
	{".tiff", "image/tiff"},
	{".txt", "text/plain"},
	{".wav", "audio/wav"},
	{".wma", "audio/x-ms-wma"},
	{".wmv", "video/x-ms-wmv"},
	{".xml", "text/xml"},
	{NULL},
};

char g_root_path[256] = "html";
int g_server_port = 8080;
int g_exit_server = 0;

/**
 * get_content_type函数用于获取文件的MIME类型
 */
static char *get_content_type(char *file)
{
	int i;
	char *ext = file + strlen(file);  // 定义一个字符指针变量ext,指向文件名的末尾
	while (ext > file && *ext != '.') // 向文件名的末尾方向遍历,找到第一个'.'字符的位置
		ext--;
	if (ext != file) // 如果找到了'.'字符,则进入下面的判断和查找逻辑
	{
		for (i = 0; g_content_type_list[i][0]; i++)
		{
			// 获取g_content_type_list中第i个元素的第一个字符,即文件扩展名
			if (strcasecmp(g_content_type_list[i][0], ext) == 0)
			{
				// 如果找到了匹配的扩展名,则返回对应的MIME类型
				return g_content_type_list[i][1];
			}
		}
	}
	// 如果没有找到匹配的MIME类型,则返回默认值"application/octet-stream"
	return "application/octet-stream";
}

/**
 * 解析 HTTP 响应中的 Range 头部和 Content-Length 头部,并填充对应的参数
 */
static void parse_range_datasize(char *str, int *partial, int *start, int *end, int *size)
{
	char *range_start, *range_end, *temp;

	// 初始化 start、end、size 指针所指向的变量为 0、0x7FFFFFFF 和 0
	*start = 0;
	*end = 0x7FFFFFFF;
	*size = 0;

	if (!str)
		return;

	// 在字符串 str 中查找 "range" 子串,并将找到的地址赋值给 range_start
	range_start = strstr(str, "range");
	// 如果 range_start 不为 NULL 并且能够在 range_start 中找到 "\r\n",将找到的地址赋值给 range_end
	if (range_start && (range_end = strstr(range_start, "\r\n")))
	{
		// 如果字符串中包含 ":"、"bytes" 和 "=" 子串,将 "=" 后面的地址偏移一位后转换为整数并赋值给 *start
		if (strstr(range_start, ":") && strstr(range_start, "bytes") && (range_start = strstr(range_start, "=")))
		{
			range_start += 1;
			*start = atoi(range_start);

			// 如果 *start 小于 0,说明起始位置是负数,因此将 "-" 后面的地址偏移一位,赋值给 range_start
			if (*start < 0)
			{
				range_start = strstr(range_start, "-");
				range_start += 1;
			}

			// 在 range_start 中查找 "-" 子串,并将找到的地址赋值给 range_start
			range_start = strstr(range_start, "-");
			if (range_start && range_start + 1 < range_end)
			{
				// 如果找到 "-" 子串并且该子串的下一个字符在 range_end 之前,将 "-" 后面的地址偏移一位后转换为整数并赋值给 *end
				range_start += 1;
				*end = atoi(range_start);
			}
		}
	}

	// 在字符串 str 中查找 "content-length" 子串,并将找到的地址赋值给 temp
	temp = strstr(str, "content-length");
	if (temp)
	{
		temp += 14;					// 将 temp 的地址偏移 14 个字符,跳过 "content-length:" 中的 "-length:" 部分
		temp = strstr(temp, ":");	// 在 temp 中查找 ":" 子串,并将找到的地址赋值给 temp
		if (temp)					// 如果 temp 不为 NULL
			*size = atoi(temp + 1); // 将 ":" 后面的地址偏移一位后转换为整数并赋值给 *size
	}
	*partial = !!range_start;
}

// 返回类型为void*的函数handle_http_request,它接受一个无类型指针作为参数.
void *handle_http_request(void *argv)
{
	THEADPOOL *tp = (THEADPOOL *)argv;								   // 将参数argv转换为THEADPOOL类型的指针,并赋值给tp.THEADPOOL可能是一个自定义的数据结构,代表一个线程池.
	int length, request_datasize = 0;								   // 定义一些整型变量,长度、请求数据的大小
	int range_start, range_end, range_size, partial = 0;			   // 定义一些整型变量,范围开始、范围结束、范围大小、部分.
	char recvbuf[1024], sendbuf[1024], cgibuf[8196], content_type[64]; // 定义一些字符数组变量,用于存储接收到的数据、发送的数据、CGI数据和内容类型.
	char *request_type = NULL, *request_path = NULL;				   // 定义一些字符指针变量,用于指向请求的类型、请求的路径
	char *url_args = NULL, *request_head = NULL, *request_data = NULL; // 定义一些字符指针变量,用于指向URL参数、请求头部和请求数据.
	int conn_fd;													   // 定义一个SOCKET类型的变量conn_fd,用于表示网络连接的句柄.

	// 循环从线程池中获取连接,当连接有效时,conn_fd不为-1,继续循环;否则结束循环.
	while ((conn_fd = threadpool_dequeue(tp)) != -1)
	{
		length = recv(conn_fd, recvbuf, sizeof(recvbuf) - 1, 0); // 从conn_fd接收数据,接收到的数据存储在recvbuf中,接收的字节数存储在length中.
		if (length <= 0)										 // 如果接收到的数据长度小于等于0,说明接收出现错误或者客户端关闭了HTTP连接.
		{
			printf("recv error or client close http connection !\n");
			fflush(stdout);
			// 打印错误信息并清空标准输出流.
			goto _next;
		}
		recvbuf[length] = '\0'; // 在接收到的数据的末尾添加字符串结束符'\0'.
		// printf("request :\n%s\n", recvbuf); fflush(stdout); //打印接收到的HTTP请求内容,并清空标准输出流.此行代码在代码中并没有出现,可能是之前版本的代码残留.

		request_head = strstr(recvbuf, "\r\n"); // 在recvbuf中查找字符串"\r\n",找到后返回该位置的指针,赋值给request_head.这是HTTP请求头部的起始位置.
		if (request_head)						// 如果找到了HTTP请求头部的起始位置.
		{
			request_head[0] = 0;							 // 将request_head指向的位置上的字符设置为0,即删除了该位置的"\r\n".
			request_head += 2;								 // 将request_head的位置移动到"\r\n\r\n"之后的位置.
			my_strlwr(request_head);						 // 对request_head指向的字符串进行小写转换,这是为了处理不同大小写的HTTP头部字段.
			request_data = strstr(request_head, "\r\n\r\n"); // 在request_head指向的字符串中查找"\r\n\r\n",找到后返回该位置的指针,赋值给request_data.这是HTTP请求数据的起始位置.
			if (request_data)								 // 如果找到了HTTP请求数据的起始位置.
			{
				request_data[0] = 0; // 将request_data指向的位置上的字符设置为0,即删除了该位置的"\r\n\r\n".
				request_data += 4;	 // 将request_data的位置移动到真正的HTTP请求数据之后的位置.
			}
			parse_params(request_head, "content-length", sendbuf, sizeof(sendbuf)); // 调用parse_params函数解析HTTP请求头部中的"content-length"参数,并将参数值存储在sendbuf中.
			request_datasize = atoi(sendbuf);										// 将sendbuf中的字符串转换为整数,并赋值给request_datasize.这是HTTP请求数据的字节数.
		}
		request_type = recvbuf; // 将recvbuf的地址赋值给request_type,request_type指向整个HTTP请求的第一行.

		/**
		 * 这段代码的作用是解析HTTP请求行中的请求路径和参数.
		 * 首先,它找到请求行中的第一个空格字符,将其替换为字符串结束符,并将该位置的指针赋值给request_path.
		 * 然后,它在request_path指向的字符串中查找第一个斜杠字符,将其替换为字符串结束符,并将该位置的指针赋值给request_path.
		 * 接着,它在request_path指向的字符串中查找第一个空格字符或问号字符,将其替换为字符串结束符.
		 * 最后,如果request_path指向的字符串为空,则将request_path指向"index.html".
		 */
		request_path = strstr(recvbuf, " "); // 从参数recvbuf中查找第一个空格字符,找到后将其替换为字符串结束符,并将该位置的指针赋值给request_path.
		if (request_path)
		{
			*request_path++ = 0;					  // 如果找到了空格字符,将该位置的字符设置为字符串结束符(null字符).
			request_path = strstr(request_path, "/"); // 在request_path指向的字符串中查找第一个斜杠字符,找到后将其替换为字符串结束符,并将该位置的指针赋值给request_path.
		}
		if (request_path)
		{
			request_path++;						  // 如果找到了斜杠字符,将该位置的字符设置为字符串结束符(null字符).
			url_args = strstr(request_path, " "); // 在request_path指向的字符串中查找第一个空格字符,找到后将其替换为字符串结束符.
			if (url_args)
				*url_args = 0;

			url_args = strstr(request_path, "?"); // 在request_path指向的字符串中查找第一个问号字符,找到后将其替换为字符串结束符.
			if (url_args)
				*url_args++ = 0;

			if (!request_path[0]) // 如果request_path指向的字符串为空,则将request_path指向"index.html".
				request_path = "index.html";
		}

		// 调用parse_range_datasize函数解析HTTP请求头中的Range头字段,并将解析结果存储在partial、range_start、range_end和range_size变量中.
		parse_range_datasize(request_head, &partial, &range_start, &range_end, &range_size);
		// printf("request_type: %s, request_path: %s, url_args: %s, range_size: %d, request_datasize: %d\n", request_type, request_path, url_args, range_size, request_datasize); fflush(stdout);

		// 调用get_file_range_size函数获取请求路径对应的文件的大小信息 并将结果存储在range_size变量中
		get_file_range_size(request_path, &range_start, &range_end, &range_size);
		if (range_size == -1) // 如果文件不存在 则构造一个HTTP响应头部信息,向客户端发送一个404 Not Found的HTTP响应体(使用g_404_page变量中的内容)
		{
			// 构造HTTP响应头部信息.
			length = snprintf(sendbuf, sizeof(sendbuf), g_ffhttpd_head1, "404 Not Found", "text/html", strlen(g_404_page));
			// printf("%s", sendbuf); fflush(stdout);
			send(conn_fd, sendbuf, length, 0);					   // 向客户端发送HTTP响应头部信息.
			send(conn_fd, g_404_page, (int)strlen(g_404_page), 0); // 向客户端发送HTTP响应体(404页面)
			goto _next;
		}

		length = request_path ? (int)strlen(request_path) : 0; // 获取request_path的长度,如果request_path为空,则长度为0

		// 如果request_path的长度大于4并且与".cgi"后缀匹配,则尝试加载并执行该文件作为CGI程序
		if (length > 4 && strcmp(request_path + length - 4, ".cgi") == 0)
		{
			void *dl = NULL;										// 动态链接库的指针
			snprintf(cgibuf, sizeof(cgibuf), "./%s", request_path); // 构造完整的文件路径
			dl = dlopen(cgibuf, RTLD_LAZY);							// 打开动态链接库

			if (dl)
			{															   // 如果成功打开,则查找并调用CGI程序的入口函数"cgimain"
				PFN_CGI_MAIN cgimain = (PFN_CGI_MAIN)dlsym(dl, "cgimain"); // 获取cgimain函数的地址
				int pagesize = 0;										   // 初始化页面大小为0

				if (cgimain)
				{																							 // 如果cgimain函数存在,则调用该函数并传递必要的参数
					strncpy(content_type, "text/html", sizeof(content_type));								 // 复制content_type字符串,确保安全
					strncpy(cgibuf, "", sizeof(cgibuf));													 // 清空cgibuf字符串,为接下来接收CGI程序的输出做准备
					pagesize = cgimain(request_type, request_path, url_args, request_data, request_datasize, // 调用CGI程序
									   content_type, sizeof(content_type), cgibuf, sizeof(cgibuf));			 // 调用CGI程序
				}
				dlclose(dl); // 关闭动态链接库

				// 根据CGI程序的输出构造HTTP响应头部,并发送给客户端
				snprintf(sendbuf, sizeof(sendbuf), g_ffhttpd_head1, "200 OK", content_type, pagesize);
				send(conn_fd, sendbuf, (int)strlen(sendbuf), 0);
				send(conn_fd, cgibuf, pagesize, 0); // 发送CGI程序的输出给客户端
			}
		}
		else if (strcmp(request_type, "GET") == 0 || strcmp(request_type, "HEAD") == 0) // 如果request_path不是以".cgi"结尾,则判断请求类型是否为GET或HEAD
		{
			// 如果不是部分请求(范围请求),则构造完整的HTTP响应头部并发送给客户端
			if (!partial)
			{
				length = snprintf(sendbuf, sizeof(sendbuf), g_ffhttpd_head1, "200 OK", get_content_type(request_path), range_size);
				send(conn_fd, sendbuf, length, 0);
			}
			else // 如果是部分请求(范围请求),则构造范围请求的HTTP响应头部并发送给客户端
			{
				length = snprintf(sendbuf, sizeof(sendbuf), g_ffhttpd_head2, range_start, range_end, range_size, get_content_type(request_path), range_size ? range_end - range_start + 1 : 0);
				send(conn_fd, sendbuf, length, 0);
			}

			// 如果请求类型是GET(而不是HEAD),则发送文件的数据给客户端
			if (strcmp(request_type, "GET") == 0)
			{
				send_file_data(conn_fd, request_path, range_start, range_end);
			}
		}

	_next:
		close(conn_fd); // 关闭socket连接
	}
	return NULL;
}

// 定义一个信号处理函数,当接收到不同的信号时,会执行相应的操作
static void sig_handler(int sig)
{
	struct sockaddr_in server_addr;	 // 定义一个结构体变量,用于存储服务器的地址信息
	int client_fd;					 // 定义一个变量,用于存储客户端的套接字文件描述符
	printf("sig_handler %d\n", sig); // 打印信号处理函数的名称和接收到的信号的编号
	fflush(stdout);					 // 刷新标准输出,确保上述的打印内容能立即显示

	switch (sig) // 根据接收到的信号类型进行不同的操作
	{
	// 当接收到 SIGINT 或 SIGTERM 信号时
	case SIGINT:
	case SIGTERM:
		g_exit_server = 1;							 // 设置全局变量 g_exit_server 为 1,表示服务器即将退出
		client_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建一个与服务器地址为 "127.0.0.1"(即本机地址)的套接字
		if (client_fd != -1)						 // 如果上述创建套接字操作没有出错
		{
			server_addr.sin_family = AF_INET;												   // 设置该套接字的地址类型为 IPv4
			server_addr.sin_port = htons(g_server_port);									   // 设置该套接字对应的服务器端口号
			server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");							   // 设置该套接字对应的服务器 IP 地址为 "127.0.0.1"
			if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == 0) // 如果套接字成功连接到了服务器
			{
				// 关闭已连接的套接字
				close(client_fd);
			}
		}
		break;
	}
}

// 主函数,程序的入口
int main(int argc, char *argv[])
{
	struct sockaddr_in server_addr, client_addr; // 定义并初始化结构体变量 server_addr 和 client_addr
	int server_fd, conn_fd;						 // 定义并初始化套接字变量 server_fd 和 conn_fd
	int addrlen = sizeof(client_addr);			 // 定义并初始化变量 addrlen,表示 client_addr 的大小
	int i;										 // 定义并初始化变量 i
	THEADPOOL thread_pool;						 // 定义并初始化线程池变量 thread_pool

	for (i = 1; i < argc; i++) // 遍历命令行参数
	{
		// 如果参数中包含 "--port=",则跳转至该字符串的后面,取出参数中的端口号,并存储到全局变量 g_server_port 中
		if (strstr(argv[i], "--port=") == argv[i])
		{
			g_server_port = atoi(argv[i] + 7);
		}
		// 否则,如果参数中包含 "--root=",则跳转至该字符串的后面,取出参数中的根路径,并复制到全局变量 g_root_path 中
		else if (strstr(argv[i], "--root=") == argv[i])
		{
			strncpy(g_root_path, argv[i] + 7, sizeof(g_root_path));
		}
	}
	// 输出端口号和根路径信息
	printf("port: %d, root: %s\n", g_server_port, g_root_path);

	// 注册信号处理函数,处理 SIGINT 和 SIGTERM 信号
	signal(SIGINT, sig_handler);
	signal(SIGTERM, sig_handler);

	server_addr.sin_family = AF_INET;				 // 设置sin_family为AF_INET,表示使用 IPv4 网络协议
	server_addr.sin_port = htons(g_server_port);	 // 设置sin_port为g_server_port,注意需要使用 htons 函数进行转换,保证网络字节序和主机字节序的一致性
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置sin_addr为INADDR_ANY,表示服务器在任何 IP 地址上接收连接
	server_fd = socket(AF_INET, SOCK_STREAM, 0);	 // 创建套接字,使用 IPv4 网络协议,流式套接字,0 协议号,表示使用默认协议

	// 如果创建套接字失败,则输出错误信息并退出程序
	if (server_fd < 0)
	{
		printf("failed to open socket !\n");
		fflush(stdout);
		exit(1);
	}
	// 将套接字绑定到 server_addr,注意需要使用 bind 函数进行绑定
	if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
	{
		printf("failed to bind !\n");
		fflush(stdout);
		exit(1);
	}
	// 将套接字设置为监听状态,最多允许 FFHTTPD_MAX_CONNECTIONS 个连接请求,注意需要使用 listen 函数进行设置
	if (listen(server_fd, FFHTTPD_MAX_CONNECTIONS) == -1)
	{
		printf("failed to listen !\n");
		fflush(stdout);
		exit(1);
	}

	// 初始化线程池,注意需要使用 threadpool_init 函数进行初始化
	threadpool_init(&thread_pool);

	// 进入一个无限循环,等待客户端的连接请求
	while (!g_exit_server)
	{
		// 接受客户端的连接请求,注意需要使用 accept 函数进行接收,返回一个新的套接字 conn_fd,同时将客户端的地址信息存储到 client_addr 中
		conn_fd = accept(server_fd, (struct sockaddr *)&client_addr, (void *)&addrlen);
		// 如果接受连接请求成功,则将 conn_fd 传入线程池进行处理
		if (conn_fd != -1)
		{
			threadpool_enqueue(&thread_pool, conn_fd);
		}
		else
		{
			printf("failed to accept !\n");
		}
	}

	threadpool_free(&thread_pool); // 释放线程池资源,注意需要使用 threadpool_free 函数进行释放
	close(server_fd);			   // 关闭套接字 server_fd
	return 0;
}
